﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MongoDB.Bson;

namespace MongoScienceDAL.Core.Tests
{
    [TestClass]
    public class ScienceRepositoryQueryIntegrationTests
    {
        #region Create Collection and Types

        public const string CollectionName = "testCollection";

        public ScienceRepository GetRepository()
        {
            TestHarness.ResetDatabase();

            var scienceCollection = new ScienceCollection
            {
                Index = 1,
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test collection",
                Name = CollectionName
            };

            scienceCollection = TestHarness.DataSource.AddCollection(scienceCollection);

            //Add some types
            var testType1 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t1",
                Name = "test1",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.Text
            };

            var testType2 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 2,
                Key = "t2",
                Name = "test2",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.DateTime
            };

            var testType3 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 3,
                Key = "t3",
                Name = "test3",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.Value
            };

            var testType4 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 4,
                Key = "t4",
                Name = "test4",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.ValueSet
            };

            var testType5 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 5,
                Key = "t5",
                Name = "test5",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.ParentLink
            };

            var testType6 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 6,
                Key = "t6",
                Name = "test6",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.ChildLinks
            };

            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType1);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType2);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType3);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType4);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType5);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType6);

            return new ScienceRepository(TestHarness.DataSource, CollectionName);
        }

        private ObjectId TestId1 { get; set; }
        private ObjectId TestId2 { get; set; }
        private ObjectId TestId3 { get; set; }
        private ObjectId TestId4 { get; set; }
        private ObjectId TestId5 { get; set; }

        public void AddDataToRepository(IScienceRepository repository)
        {
            TestId1 = ObjectId.GenerateNewId();
            TestId2 = ObjectId.GenerateNewId();
            TestId3 = ObjectId.GenerateNewId();
            TestId4 = ObjectId.GenerateNewId();
            TestId5 = ObjectId.GenerateNewId();

            var entry1 = new Dictionary<string, object>
                             {
                                 {"t1","hello i am a test string"},
                                 {"t2",DateTime.Today},
                                 {"t3",1500d},
                                 {"t5",TestId1},
                                 {"t6",new List<ObjectId>{TestId3,TestId4,TestId5}}
                             };

            var entry2 = new Dictionary<string, object>
                             {
                                 {"t1","hello i am a test string also"},
                                 {"t2",DateTime.Today.Subtract(new TimeSpan(1,0,0,0))},
                                 {"t3",2000d},
                                 {"t4",new List<double>{10,20,30,40}},
                                 {"t5",TestId3},
                                 {"t6",new List<ObjectId>{TestId1}}
                             };

            var entry3 = new Dictionary<string, object>
                             {
                                 {"t1","goodbye i am a test string"},
                                 {"t2",DateTime.Today.AddDays(1)},
                                 {"t3",3000d},
                                 {"t5",TestId2},
                                 {"t6",new List<ObjectId>{TestId1,TestId2,TestId3}}
                             };

            var entry4 = new Dictionary<string, object>
                             {
                                 {"t1","hi i am a awesome test string"},
                                 {"t2",DateTime.Today.AddDays(2)},
                                 {"t3",500d},
                                 {"t4",new List<double>{1,2,3,4}},
                                 {"t6",new List<ObjectId>{TestId2}}
                             };

            EntryId1 = repository.Create(entry1);
            EntryId2 = repository.Create(entry2);
            EntryId3 = repository.Create(entry3);
            EntryId4 = repository.Create(entry4);
        }

        protected ObjectId EntryId4 { get; set; }
        protected ObjectId EntryId3 { get; set; }
        protected ObjectId EntryId2 { get; set; }
        protected ObjectId EntryId1 { get; set; }

        #endregion

        [TestMethod]
        public void ValueEqualsMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddValueEquals("t3", 1500);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t1"], "hello i am a test string");
        }

        [TestMethod]
        public void ValueGreaterThanMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddValueGreaterThan("t3", 2000);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t1"], "goodbye i am a test string");
        }

        [TestMethod]
        public void ValueGreaterThanOrEqualToMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddValueGreaterThanOrEqualTo("t3", 2000);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t1"], "hello i am a test string also");
            Assert.AreEqual(results[1].Data["t1"], "goodbye i am a test string");
        }

        [TestMethod]
        public void ValueLessThanMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddValueLessThan("t3", 1500);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t1"], "hi i am a awesome test string");
        }

        [TestMethod]
        public void ValueLessThanOrEqualToMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddValueLessThanOrEqualTo("t3", 1500);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t1"], "hello i am a test string");
            Assert.AreEqual(results[1].Data["t1"], "hi i am a awesome test string");
        }

        [TestMethod]
        public void TextContainsMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddTextContains("t1", "awesome");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 500d);
        }

        [TestMethod]
        public void TextStartsWithsMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddTextStartsWith("t1", "hello");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 2000d);
        }

        [TestMethod]
        public void TextEndsWithMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddTextEndsWith("t1", "also");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 2000d);
        }

        [TestMethod]
        public void TextEqualsMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddTextEquals("t1", "goodbye i am a test string");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 3000d);
        }

        [TestMethod]
        public void TextRegexMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddTextRegex("t1", "^hi");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 500d);
        }

        [TestMethod]
        public void HasFieldMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddHasField("t4");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 2000d);
            Assert.AreEqual(results[1].Data["t3"], 500d);
        }

        [TestMethod]
        public void DosentHaveFieldMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDosentHaveField("t4");

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 3000d);
        }

        [TestMethod]
        public void DateEqualsMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateEquals("t2",DateTime.Today);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
        }

        [TestMethod]
        public void DateGreaterThanMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateGreaterThan("t2", DateTime.Today);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 3000d);
            Assert.AreEqual(results[1].Data["t3"], 500d);
        }

        [TestMethod]
        public void DateGreaterThanOrEqualsToMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateGreaterThanOrEqualTo("t2", DateTime.Today);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 3);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 3000d);
            Assert.AreEqual(results[2].Data["t3"], 500d);
        }

        [TestMethod]
        public void DateLessThanMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateLessThan("t2", DateTime.Today);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 2000d);
        }

        [TestMethod]
        public void DateLessThanOrEqualsToMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateLessThanOrEqualTo("t2", DateTime.Today);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 2000d);
        }

        [TestMethod]
        public void DateInsideRangeMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateInsideRange("t2", DateTime.Today,DateTime.Today.AddDays(2));

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 3);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 3000d);
            Assert.AreEqual(results[2].Data["t3"], 500d);
        }

        [TestMethod]
        public void DateOutsideRangeMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddDateOutsideRange("t2", DateTime.Today, DateTime.Today.AddDays(1));

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 2000d);
            Assert.AreEqual(results[1].Data["t3"], 500d);
        }

        [TestMethod]
        public void ObjectIdEqualsMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            queryCriteria.AddObjectIdEquals(EntryId1);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 1500d);

            queryCriteria.Filters.Clear();

            queryCriteria.AddObjectIdEquals(EntryId3);

            results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 3000d);
        }

        [TestMethod]
        public void ObjectIdEqualsAnyMatches()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            var objectsToGet = new List<ObjectId> {EntryId2, EntryId4};
            queryCriteria.AddObjectIdEqualsAny(objectsToGet);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 2000d);
            Assert.AreEqual(results[1].Data["t3"], 500d);
        }

        [TestMethod]
        public void CountOperatesLikeQueryButReturnsNumberOfRecordsInstead()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            var objectsToGet = new List<ObjectId> { EntryId2, EntryId4 };
            queryCriteria.AddObjectIdEqualsAny(objectsToGet);

            var results = repo.Count(queryCriteria);

            Assert.AreEqual(2, results);
        }

        [TestMethod]
        public void ExtensionMethodParentIdEquals()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();
            queryCriteria.AddParentIdEquals("t5", TestId3);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 2000d);
        }

        [TestMethod]
        public void ExtensionMethodParentIdEqualsAny()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();
            queryCriteria.AddParentIdEqualsAny("t5", new List<ObjectId> {TestId1, TestId2});

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 2);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 3000d);
        }

        [TestMethod]
        public void ExtensionMethodChildIdsContain()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();
            queryCriteria.AddChildIdsContain("t6", TestId5);

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 1);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
        }

        [TestMethod]
        public void ExtensionMethodChildIdsContainAny()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();
            queryCriteria.AddChildIdsContainAny("t6", new List<ObjectId>{TestId2,TestId3});

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 3);
            Assert.AreEqual(results[0].Data["t3"], 1500d);
            Assert.AreEqual(results[1].Data["t3"], 3000d);
            Assert.AreEqual(results[2].Data["t3"], 500d);
        }

        [TestMethod]
        public void QuerySelectorsWhenEmptyShouldReturnNormalAmountOfFields()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 4);
            
            Assert.AreEqual(5, results[0].Data.Count);
            Assert.AreEqual(6, results[1].Data.Count);
            Assert.AreEqual(5, results[2].Data.Count);
            Assert.AreEqual(5, results[3].Data.Count);
        }

        [TestMethod]
        public void QueryPagedReturnsPageResultCorrectly()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            //get everything
            var queryCriteria = new QueryCriteria();

            var pagedResults = repo.QueryPaged(queryCriteria, 1, 2);

            Assert.AreEqual(4, pagedResults.TotalCount);
            Assert.AreEqual(2, pagedResults.TotalPages);
            Assert.AreEqual(1, pagedResults.PageIndex);
            Assert.AreEqual(2, pagedResults.PageSize);
            Assert.AreEqual(false, pagedResults.HasNextPage);
            Assert.AreEqual(true, pagedResults.HasPreviousPage);
        }

        [TestMethod]
        public void QuerySelectorSelectsOnFieldsThatWeWant()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var queryCriteria = new QueryCriteria();
            queryCriteria.Selectors.Add(new QuerySelector { Key = "t1" });
            queryCriteria.Selectors.Add(new QuerySelector { Key = "t4" });

            var results = repo.Query(queryCriteria);

            Assert.IsTrue(results.Count == 4);

            Assert.AreEqual(1, results[0].Data.Count);
            Assert.AreEqual(2, results[1].Data.Count);
            Assert.AreEqual(1, results[2].Data.Count);
            Assert.AreEqual(2, results[3].Data.Count);
        }

        [TestMethod]
        public void QueryAndUpdateWhenSelectorsAreUsedDosentMessUpLikeIThinkItMay()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            //this verifies that update works in the standard way no selectors
            //and returning the complete entry with all fields intact.
            var entry = repo.Retrieve(EntryId1);
            Assert.AreEqual(1500d, entry.Data["t3"]);
            entry.Data["t3"] = 100d;
            repo.Update(entry);
            var newEntry = repo.Retrieve(EntryId1);
            Assert.AreEqual(100d, newEntry.Data["t3"]);

            //this is with a selector and filter that should return the same entry
            //but with only the 't3' field
            var entry2 = repo.Query(new QueryCriteria().AddObjectIdEquals(EntryId1).AddSelector("t3")).First();
            Assert.AreEqual(100d, entry2.Data["t3"]);
            entry2.Data["t3"] = 300d;
            repo.Update(entry2);
            var newEntry2 = repo.Retrieve(EntryId1);
            Assert.AreEqual(300d, newEntry2.Data["t3"]);
        }

        [TestMethod]
        public void RetreiveFieldGetsTheCorrectTypeOfObject()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var t1Object = repo.RetrieveField(EntryId2, "t1");
            var t2Object = repo.RetrieveField(EntryId2, "t2");
            var t3Object = repo.RetrieveField(EntryId2, "t3");
            var t4Object = repo.RetrieveField(EntryId2, "t4");
            var t5Object = repo.RetrieveField(EntryId2, "t5");
            var t6Object = repo.RetrieveField(EntryId2, "t6");

            Assert.AreEqual("hello i am a test string also", t1Object);
            Assert.AreEqual(DateTime.Today.Subtract(new TimeSpan(1, 0, 0, 0)), t2Object);
            Assert.AreEqual(2000d, t3Object);
            Assert.AreEqual(4, ((List<double>)t4Object).Count);
            Assert.AreEqual(TestId3, t5Object);
            Assert.AreEqual(TestId1, ((List<ObjectId>)t6Object)[0]);

        }

        [TestMethod]
        public void UpdateFieldUpdatesTheCorrectFieldCorrectly()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var first = (double)repo.RetrieveField(EntryId2, "t3");

            Assert.AreEqual(2000d, first);

            repo.UpdateField(EntryId2, "t3", 970d);

            var second = (double) repo.RetrieveField(EntryId2, "t3");

            Assert.AreEqual(970d, second);
        }

        [TestMethod]
        public void TestProblemWithUpdateWhereItWasDroppingAllOtherFieldsThanTheUpdatingField()
        {
            var repo = GetRepository();
            AddDataToRepository(repo);

            var entryOriginal = repo.Retrieve(EntryId1);

            Assert.AreEqual("hello i am a test string", entryOriginal.Data["t1"]);
            Assert.AreEqual(1500d, entryOriginal.Data["t3"]);
            
            var field1 = repo.RetrieveField(EntryId1, "t1");
            var field2 = repo.RetrieveField(EntryId1, "t3");

            Assert.AreEqual("hello i am a test string", field1);
            Assert.AreEqual(1500d, field2);

            repo.UpdateField(EntryId1, "t3", 999d);//this used to drop the other fields for silly reasons

            field1 = repo.RetrieveField(EntryId1, "t1"); //crash
            field2 = repo.RetrieveField(EntryId1, "t3"); //work

            Assert.AreEqual("hello i am a test string", field1);
            Assert.AreEqual(999d, field2);
        }
    }
}
